Logotipo da disciplina PAG014903

Fundamentos de Programação em Python

v0.0.2

Padrão

Manipulação de Dados com Pandas e GeoPandas

Saia das estruturas básicas e explore bibliotecas poderosas para ler, limpar e analisar dados tabulares e geoespaciais antes de visualizá-los em mapas interativos.

Você vai aprender a

  • Ler e explorar arquivos CSV com o Pandas.
  • Converter e reprojetar dados espaciais com GeoPandas.
  • Executar consultas espaciais para responder perguntas de localização.

Arquivos necessários para acompanhar a aula

Dataset de referência

Contém dados fictícios de balneabilidade para algumas praias de Florianópolis, incluindo nome, latitude, longitude e situação, ideais para as consultas com Pandas e GeoPandas.

Baixar Pontos.csv

Shapefile oficial do IBGE

Arquivo SC_Municipios_2022 com as geometrias municipais atualizadas. O download ocorre a partir dos servidores públicos do IBGE.

O GeoPandas é capaz de ler um shapefile diretamente de um arquivo .zip, desde que haja apenas um conjunto de arquivos de shapefile dentro dele. Caso o .zip contenha múltiplos shapefiles ou uma estrutura de pastas complexa, o ideal é extrair todo o conteúdo para uma pasta local, como arquivos/SC_Municipios_2022/.
Nota importante: Ao extrair arquivos, sempre mantenha os nomes originais dos componentes do shapefile (.shp, .dbf, .shx, .prj etc.) juntos na mesma pasta. O GeoPandas precisa de todos eles para montar a camada corretamente.
Baixar SC_Municipios_2022.zip

1. Introdução: Por que precisamos de mais que listas e dicionários?

Listas e dicionários nos ajudaram a organizar dados básicos em Python, como uma coleção de praias com seus atributos.

dados_praias = [
    {"Praia": "Praia da Barra", "Longitude": -48.573928, "Latitude": -27.600218, "Situação": "Boa"},
    {"Praia": "Praia Brava", "Longitude": -48.574293, "Latitude": -27.600404, "Situação": "Boa"},
    # ... e assim por diante
]

Lembre-se da nossa analogia das "caixinhas" para variáveis. Listas e dicionários nos permitem organizar várias delas, mas carregar cada caixinha individualmente se torna impossível quando lidamos com milhares de registros. Precisamos de ferramentas melhores: um carrinho para transportar dezenas de caixas de uma vez, ou uma empilhadeira para organizar um armazém inteiro. Para escalar o trabalho com dados, o princípio é o mesmo. Recorremos a ferramentas feitas sob medida para análise de dados: Pandas funciona como nosso carrinho de mão superpoderoso para tabelas, e GeoPandas é a nossa empilhadeira especializada para dados espaciais.

2. Instalação das bibliotecas necessárias

Antes de trabalhar com dados tabulares e espaciais, precisamos instalar as bibliotecas Pandas e GeoPandas. Vamos usar o pip, que é o gerenciador de pacotes oficial do Python.

2.1 Instalando o Pandas

O Pandas é relativamente simples de instalar. Abra o terminal (ou prompt de comando) e execute:

pip install pandas

2.2 Instalando o GeoPandas

O GeoPandas depende de várias bibliotecas geoespaciais. A instalação pode ser feita de duas formas:

Opção 1: Instalação direta (recomendada)

pip install geopandas

Esta opção instala automaticamente todas as dependências necessárias.

Opção 2: Se houver problemas de dependência

pip install fiona shapely pyproj matplotlib
pip install geopandas

Instala as dependências principais (incluindo matplotlib para gráficos) primeiro, depois o GeoPandas.

2.3 Verificando a instalação

Para confirmar que tudo foi instalado corretamente, teste as importações:

import pandas as pd
import geopandas as gpd

print(f"Pandas versão: {pd.__version__}")
print(f"GeoPandas versão: {gpd.__version__}")
print("✅ Todas as bibliotecas instaladas com sucesso!")

3. Pandas: Sua planilha superpoderosa no Python

Pandas é a biblioteca central para manipular estruturas tabulares em Python. Ele concentra grandes volumes de dados em DataFrames, oferecendo operações vetorizadas eficientes. Se pensarmos rapidamente na analogia das caixinhas, ele agrupa e empilha essas unidades em um carrinho único, pronto para circular pelo fluxo de análise. Podemos enxergar o processo principal nestas três etapas:

Importar dados em lote

Lê arquivos CSV, planilhas, bancos SQL e formatos columnar como Parquet, centralizando tudo em um DataFrame com colunas nomeadas e índices consistentes.

Normalizar tipos e colunas

Detecta automaticamente valores numéricos, categóricos ou temporais, permitindo conversões, limpeza e validação de dados com poucas linhas.

Explorar e analisar rapidamente

Filtra, agrupa, calcula estatísticas e integra com visualizações, respondendo perguntas analíticas de milhares de registros em segundos.

Em essência, o Pandas viabiliza pipelines de dados escaláveis, substituindo laços manuais por operações declarativas, legíveis e de alto desempenho.

3.1 A estrutura principal: o DataFrame

Imagine o DataFrame como a estrela do show no Pandas. A maneira mais fácil de entender essa estrutura é vê-la como uma tabela ou planilha digital superpoderosa.

Pense assim: cada DataFrame é como uma folha de Excel, mas que você controla com código! Ele organiza suas "caixinhas" de dados (suas variáveis) em duas dimensões perfeitas:

  • Linhas: cada linha é um registro completo. Pense nos dados de uma pessoa, um produto ou um evento. É o seu item individual.
  • Colunas: cada coluna é um tipo específico de informação. Por exemplo, uma coluna pode ser "Nome", outra "Idade", "Preço" ou "Data". É a categoria da sua informação.

O segredo das colunas está nos rótulos e nos tipos. Cada coluna tem um rótulo claro (o nome que você dá, como "Nome do Cliente" ou "Valor Total") e, o mais importante, armazena dados de um tipo apropriado.

Veja um exemplo: uma coluna "Idade" só terá números inteiros. Já uma coluna "Produto" terá textos, e "Data da Venda" terá o formato certinho de data.

Essa organização é crucial! Não só facilita a leitura e a interpretação dos dados, mas também garante que o Pandas realize operações corretamente. Afinal, você não tentaria somar um "nome" com um "preço", não é? O DataFrame cuida disso para você.

3.2 Lendo dados de um arquivo CSV

Carregar dados tabulares é simples com pd.read_csv(). Para esta atividade, utilize o arquivo Pontos.csv que se encontra no início desta página.

# 1. Importar a biblioteca pandas
import pandas as pd  # "pd" é o apelido padrão

# 2. Ler o arquivo CSV e carregar os dados em um DataFrame
try:
    df_praias = pd.read_csv('arquivos/Pontos.csv')

    # 3. Explorar os dados que foram carregados
    print("--- As 5 primeiras linhas do nosso DataFrame ---")
    print(df_praias.head())  # .head() mostra as primeiras 5 linhas

    print("\n--- Informações sobre as colunas e os tipos de dados ---")
    print(df_praias.info())  # .info() resume a estrutura do DataFrame

except FileNotFoundError:
    print("Erro: O arquivo 'arquivos/Pontos.csv' não foi encontrado.")
    print("Certifique-se de que ele está na pasta 'arquivos/' do seu projeto.")

3.3 Operações básicas com DataFrames

Depois de carregar os dados, o Pandas simplifica consultas, cálculos e filtragens que antes exigiriam muitos laços for.

import pandas as pd

try:
    df_praias = pd.read_csv('arquivos/Pontos.csv')

    # a) Selecionar uma coluna específica (retorna uma Series)
    nomes_das_praias = df_praias['Praia']
    print("--- Nomes de todas as praias ---")
    print(nomes_das_praias)

    # b) Calcular estatísticas básicas de uma coluna numérica
    latitude_media = df_praias['Latitude'].mean()
    print(f"\n--- A latitude média é: {latitude_media:.4f} ---")

    # c) Filtrar o DataFrame pela Situação
    praias_ruins = df_praias[df_praias['Situação'] == 'Ruim']
    print("\n--- Praias com situação 'Ruim' ---")
    print(praias_ruins)

except FileNotFoundError:
    print("Erro: O arquivo 'arquivos/Pontos.csv' não foi encontrado.")

Note como cálculos e filtros robustos cabem em poucas linhas, liberando tempo para análises mais profundas.

4. GeoPandas: adicionando inteligência geográfica

GeoPandas expande o Pandas para entender colunas com latitudes e longitudes como geometrias reais. Tudo isso apoiado pela biblioteca pyproj para transformações de coordenadas.

4.1 Lendo um arquivo Shapefile

Shapefiles são formatos clássicos do geoprocessamento, contendo atributos e geometria. GeoPandas lê essas estruturas com read_file().

import geopandas as gpd

# Caminho para o arquivo .shp (extraído do ZIP do IBGE)
caminho_shapefile = 'arquivos/SC_Municipios_2022/SC_Municipios_2022.shp'

try:
    gdf_municipios = gpd.read_file(caminho_shapefile)

    print("--- As 5 primeiras linhas do nosso GeoDataFrame ---")
    print(gdf_municipios.head())

    print("\n--- Informações sobre o GeoDataFrame ---")
    gdf_municipios.info()
    # Repare na coluna "geometry", exclusiva dos GeoDataFrames

except Exception as e:
    print(f"Não foi possível ler o arquivo Shapefile: {e}")
    print("Verifique se o caminho está correto e se o arquivo não está corrompido.")

4.2 Visualização rápida: a prova real do geoprocessamento

Depois de carregar ou criar um GeoDataFrame, nada melhor do que desenhar um mapa para validar se os dados fazem sentido. O método .plot() usa Matplotlib por trás dos panos e gera uma visualização estática imediata, perfeita para conferir o Sistema de Referência de Coordenadas (CRS), detectar geometrias distorcidas ou confirmar se o recorte espacial bate com o esperado.

Começamos com o cenário mais simples: carregar o shapefile e chamar plot() sem ajustes extras. Ainda assim, vale sempre adicionar um título e rótulos nos eixos para contextualizar.

import geopandas as gpd
import matplotlib.pyplot as plt

caminho_shapefile = 'shapefiles/SC_Municipios_2022.shp'

try:
    # Carregar o shapefile usando GeoPandas
    gdf_municipios = gpd.read_file(caminho_shapefile)

    # Plotar o mapa simples
    gdf_municipios.plot()
    plt.title("Mapa Simples dos Municípios de SC")  # adicionar título
    plt.xlabel("Longitude")                         # adicionar rótulo ao eixo x
    plt.ylabel("Latitude")                          # adicionar rótulo ao eixo y
    plt.show()                                       # exibir o gráfico

except Exception as e:
    print(f"Ocorreu um erro ao plotar o mapa: {e}")

Quando precisamos combinar camadas, podemos desenhar múltiplos GeoDataFrames no mesmo eixo (ax). Abaixo filtramos apenas o município de Florianópolis e sobrepomos os pontos de balneabilidade do arquivo Pontos.csv, todos convertidos para WGS84 (EPSG:4326) para garantir alinhamento.

import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt

caminho_municipios = 'shapefiles/SC_Municipios_2022.shp'
caminho_pontos = 'arquivos/Pontos.csv'

try:
    gdf_municipios = gpd.read_file(caminho_municipios)

    # Filtrar apenas Florianópolis e garantir que esteja em WGS84
    gdf_floripa = gdf_municipios[gdf_municipios['NM_MUN'] == 'Florianópolis']
    if gdf_floripa.empty:
        raise ValueError('Não há registros de Florianópolis na camada municipal.')
    gdf_floripa = gdf_floripa.to_crs('EPSG:4326')

    # Transformar o CSV em GeoDataFrame de pontos
    df_pontos = pd.read_csv(caminho_pontos)
    gdf_pontos = gpd.GeoDataFrame(
        df_pontos,
        geometry=gpd.points_from_xy(df_pontos['Longitude'], df_pontos['Latitude']),
        crs='EPSG:4326'
    )

    fig, ax = plt.subplots(figsize=(9, 8))
    gdf_floripa.plot(
        ax=ax,
        color='#bbf7d0',
        edgecolor='#047857',
        linewidth=0.8,
        label='Florianópolis'
    )

    gdf_pontos.plot(
        ax=ax,
        color='#dc2626',
        markersize=45,
        marker='o',
        label='Pontos monitorados',
        alpha=0.85
    )

    ax.set_title('Pontos de balneabilidade dentro de Florianópolis', fontsize=15, fontweight='bold')
    ax.set_xlabel('Longitude')
    ax.set_ylabel('Latitude')
    ax.legend()
    ax.set_aspect('equal')
    plt.show()

except Exception as e:
    print(f"Ocorreu um erro ao montar a figura: {e}")

Customizando a visualização

O .plot() aceita parâmetros úteis para criar mapas temáticos simples direto no notebook ou no script.

color e edgecolor

Controlam o preenchimento e o contorno dos polígonos.

figsize

Define o tamanho da figura ao usar plt.subplots().

column

Aplica um coroplético simples com base nos valores de uma coluna.

cmap

Escolhe a paleta de cores usada no coroplético.

legend

Mostra ou oculta a legenda quando usamos o parâmetro column.

Na sequência, a seção 4.3 mostra como manipular e converter formatos a partir desses dados já conferidos. Se quiser ver um exemplo de sobreposição de camadas com pontos e polígonos, confira o bloco opcional ao final desta parte (seção 4.10).

4.3 Manipulação e conversão de formatos

GeoPandas também funciona como um tradutor entre formatos espaciais. Converter Shapefile para GeoJSON, por exemplo, é direto.

import geopandas as gpd

caminho_shapefile = 'arquivos/SC_Municipios_2022/SC_Municipios_2022.shp'
caminho_saida_geojson = 'saidas/municipios_sc.geojson'

try:
    gdf_municipios = gpd.read_file(caminho_shapefile)
    gdf_municipios.to_file(caminho_saida_geojson, driver='GeoJSON')
    print(f"Arquivo GeoJSON salvo com sucesso em: {caminho_saida_geojson}")

except Exception as e:
    print(f"Ocorreu um erro durante o processo: {e}")

4.4 Transformando sistemas de coordenadas

Converter entre sistemas geográficos (graus) e projetados (metros) é essencial para cálculos de área e distância. GeoPandas simplifica a reprojeção via .to_crs().

import geopandas as gpd

caminho_shapefile = 'shapefiles/SC_Municipios_2022.shp'

try:
    # Carregar o shapefile usando GeoPandas
    gdf_municipios = gpd.read_file(caminho_shapefile)
    gdf_utm = gdf_municipios  # ponto de partida caso o CRS já esteja correto

    # verificar se o CRS está definido
    if gdf_municipios.crs is None:
        # criar uma exceção caso o CRS não esteja definido
        raise ValueError("O GeoDataFrame não possui um CRS definido. Por favor, defina o CRS antes de reprojetar.")

    # Reprojetar para UTM (EPSG:31982) se o CRS atual for diferente
    elif gdf_municipios.crs.to_string() != "EPSG:31982":
        print("Reprojetando para UTM (EPSG:31982)...")
        gdf_utm = gdf_municipios.to_crs("EPSG:31982")

    # Conferir o resultado
    print("\n--- Geometrias após a conversão para UTM (coordenadas em metros) ---")
    print(gdf_utm.head()) # exibir as primeiras linhas do GeoDataFrame reprojetado

except Exception as e:
    print(f"Ocorreu um erro durante a reprojeção: {e}")

4.5 Exportando para GeoPackage e padronizando o CRS

Uma vez que o CRS está conferido (seção 4.4), podemos entregar os dados em formatos modernos. O GeoPackage (.gpkg) guarda múltiplas camadas no mesmo arquivo e aceita geometrias em qualquer projeção. O snippet abaixo reprojeta tudo para SIRGAS 2000 / UTM zona 22S (EPSG:31982) e exporta cada camada definida em uma lista fixa.

import geopandas as gpd
from pathlib import Path

crs_destino = 'EPSG:31982'  # SIRGAS 2000 / UTM 22S
destino_gpkg = Path('saidas/base_geoespacial.gpkg')

camadas = [
    {
        'origem': 'arquivos/SC_Municipios_2022/SC_Municipios_2022.shp',
        'layer': 'municipios_sc'
    },
    {
        'origem': 'arquivos/exemplos/areas_preservacao.shp',
        'layer': 'areas_preservacao'
    }
]

destino_gpkg.parent.mkdir(parents=True, exist_ok=True)

for item in camadas:
    origem = Path(item['origem'])
    layer = item['layer']

    try:
        gdf = gpd.read_file(origem)
        gdf_utm = gdf.to_crs(crs_destino)
        gdf_utm.to_file(destino_gpkg, layer=layer, driver='GPKG')
        print(f"✅ Camada '{layer}' exportada para {destino_gpkg.name} em {crs_destino}")
    except Exception as erro:
        print(f"⚠️ Não foi possível processar {origem.name}: {erro}")

Adapte a lista camadas aos arquivos do seu projeto — substitua, por exemplo, arquivos/exemplos/areas_preservacao.shp pelo caminho real da sua camada temática. O padrão é sempre ler, reprojetar e gravar no mesmo loop; assim o time economiza cliques e mantém todas as saídas alinhadas ao sistema oficial da disciplina.

4.6 Transformando DataFrame em GeoDataFrame

Do CSV ao mapa: dando consciência espacial aos dados

Uma das tarefas mais corriqueiras do geoprocessamento é transformar dados tabulares em camadas espaciais. Planilhas de clientes, listas de ocorrências ou arquivos .csv exportados do GNSS chegam cheios de coordenadas, mas para o Pandas são apenas números. Para desbloquear cálculos de distância, buffers ou consultas espaciais, precisamos converter o DataFrame em GeoDataFrame, a estrutura que entende geometrias.

Pense nesse processo como uma tradução em três atos:

  1. Carregar os dados tabulares: usamos o Pandas para organizar o arquivo em linhas e colunas.
  2. Construir a geometria: a partir das colunas de coordenadas, geramos objetos Point que representam cada registro no espaço.
  3. Criar o GeoDataFrame: combinamos dados e geometria e informamos o Sistema de Referência de Coordenadas (CRS) correto — etapa crucial para que os pontos apareçam no lugar certo do mundo.

Imagine um arquivo levantamento_rtk.csv, exportado de um receptor Topcon após o trabalho de campo:

Ponto,Leste(X),Norte(Y),Elevacao,Descricao
P01,754231.56,6941234.89,12.34,Poste
P02,754235.12,6941238.45,12.56,Meio-fio
P03,754240.03,6941242.18,12.61,Esquina

O GeoPandas oferece a função points_from_xy(), que lê diretamente as colunas de coordenadas e monta os pontos para nós. Em seguida, informamos que esses valores estão em SIRGAS 2000 / UTM zona 22S (EPSG:31982) e ganhamos uma camada espacial pronta para análise.

import pandas as pd
import geopandas as gpd

try:
    df = pd.read_csv('arquivos/levantamento_rtk.csv')

    geometria = gpd.points_from_xy(df['Leste(X)'], df['Norte(Y)'])
    gdf = gpd.GeoDataFrame(df, geometry=geometria, crs='EPSG:31982')

    print('GeoDataFrame criado com sucesso!')
    print(gdf.head())
    print(f'CRS informado: {gdf.crs}')

except FileNotFoundError:
    print('Arquivo levantamento_rtk.csv não encontrado. Verifique o caminho.')
except Exception as erro:
    print(f'Erro ao criar o GeoDataFrame: {erro}')

A partir daqui, o conjunto está apto a receber buffers, participar de spatial join, ser visualizado com .plot() ou exportado para GeoPackage (seção 4.5). O segredo é garantir que o CRS esteja correto desde o início.

4.7 Spatial Joins (Junções Espaciais)

As junções espaciais permitem cruzar dados de pontos com polígonos, respondendo perguntas como "a qual município pertence cada praia?" ou "quantas praias estão em cada cidade?". É uma operação fundamental no geoprocessamento.

import pandas as pd
import geopandas as gpd
from shapely.geometry import Point

try:
    # 1. Criar GeoDataFrame das praias
    df_praias = pd.read_csv('arquivos/Pontos.csv')
    geometry_praias = [Point(xy) for xy in zip(df_praias['Longitude'], df_praias['Latitude'])]
    gdf_praias = gpd.GeoDataFrame(df_praias, geometry=geometry_praias, crs='EPSG:4326')

    # 2. Carregar shapefile dos municípios
    gdf_municipios = gpd.read_file('arquivos/SC_Municipios_2022/SC_Municipios_2022.shp')

    # 3. Realizar a junção espacial
    # Cada praia será associada ao município que a contém
    praias_com_municipio = gpd.sjoin(gdf_praias, gdf_municipios, how='left', predicate='within')

    print("--- Praias com informações do município ---")
    print(praias_com_municipio[['Praia', 'Situação', 'NM_MUN']].head())

    # 4. Análise: quantas praias por município?
    contagem_por_municipio = praias_com_municipio.groupby('NM_MUN').size()
    print(f"\n--- Praias por município ---")
    print(contagem_por_municipio)

    # 5. Análise: situação das praias por município
    situacao_por_municipio = praias_com_municipio.groupby(['NM_MUN', 'Situação']).size().unstack(fill_value=0)
    print(f"\n--- Situação das praias por município ---")
    print(situacao_por_municipio)

except Exception as e:
    print(f"Erro na junção espacial: {e}")

4.8 Operações geométricas: criando buffers

Buffers são áreas de influência ao redor de geometrias. São úteis para análises de proximidade, como "quais praias estão a menos de 500 metros de uma escola?" ou "qual a área de impacto de um empreendimento?".

import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
import matplotlib.pyplot as plt

try:
    # 1. Criar GeoDataFrame das praias
    df_praias = pd.read_csv('arquivos/Pontos.csv')
    geometry_praias = [Point(xy) for xy in zip(df_praias['Longitude'], df_praias['Latitude'])]
    gdf_praias = gpd.GeoDataFrame(df_praias, geometry=geometry_praias, crs='EPSG:4326')

    # 2. Reprojetar para um sistema em metros (necessário para buffers em metros)
    gdf_praias_utm = gdf_praias.to_crs('EPSG:31982')  # UTM Zone 22S

    # 3. Criar buffers de 500 metros ao redor de cada praia
    gdf_praias_utm['buffer_500m'] = gdf_praias_utm.geometry.buffer(500)

    # 4. Criar um novo GeoDataFrame só com os buffers
    gdf_buffers = gdf_praias_utm.copy()
    gdf_buffers['geometry'] = gdf_buffers['buffer_500m']
    gdf_buffers = gdf_buffers.drop('buffer_500m', axis=1)

    # 5. Reprojetar de volta para visualização
    gdf_praias_plot = gdf_praias_utm.to_crs('EPSG:4326')
    gdf_buffers_plot = gdf_buffers.to_crs('EPSG:4326')

    print(f"Criados {len(gdf_buffers)} buffers de 500m")
    
    # 6. Visualizar (opcional)
    fig, ax = plt.subplots(figsize=(12, 8))
    gdf_buffers_plot.plot(ax=ax, color='lightblue', alpha=0.5, label='Buffer 500m')
    gdf_praias_plot.plot(ax=ax, color='red', markersize=50, label='Praias')
    ax.set_title('Praias e suas áreas de influência (500m)')
    ax.legend()
    plt.show()

    # 7. Calcular área total dos buffers
    area_total_buffers = gdf_buffers.geometry.area.sum()
    print(f"Área total dos buffers: {area_total_buffers:.2f} m²")

except Exception as e:
    print(f"Erro ao criar buffers: {e}")

4.9 Introdução a relações topológicas (consultas espaciais)

Consultas espaciais respondem perguntas sobre interseções, contenções e vizinhança entre objetos geográficos. GeoPandas usa internamente a biblioteca Shapely para esses cálculos.

import geopandas as gpd
from shapely.geometry import Point

caminho_shapefile = 'arquivos/SC_Municipios_2022/SC_Municipios_2022.shp'

try:
    gdf_municipios = gpd.read_file(caminho_shapefile)

    # 1. Criar um ponto para o IFSC (longitude, latitude)
    ponto_ifsc = Point(-48.54235, -27.59397)

    # 2. Consulta espacial: qual município contém o ponto?
    municipio_ifsc = gdf_municipios[gdf_municipios.contains(ponto_ifsc)]

    # 3. Exibir o resultado
    if not municipio_ifsc.empty:
        nome_municipio = municipio_ifsc['NM_MUN'].iloc[0]
        print(f"O ponto do IFSC está localizado no município de: {nome_municipio}")
    else:
        print("Não foi encontrado nenhum município contendo o ponto especificado.")

except Exception as e:
    print(f"Ocorreu um erro durante a consulta espacial: {e}")

4.10 Operações de Sobreposição (Overlay)

Enquanto as junções espaciais (sjoin) enriquecem os atributos de uma camada com base na relação espacial com outra, as operações de sobreposição (overlay) criam novas geometrias a partir da interação entre as camadas.

Imagine combinar municípios e áreas de preservação ambiental. Com overlay, você responde perguntas como:

  • Interseção (intersection): Quais porções de cada área de preservação estão dentro de um município? O resultado gera novos polígonos apenas nas áreas de sobreposição.
  • União (union): Qual geometria representa a combinação de municípios e áreas preservadas? O resultado funde as duas camadas em uma única malha.
  • Diferença (difference): Qual parte do município fica fora das áreas de preservação? O resultado mantém a primeira camada com as regiões da segunda "subtraídas".

Essas operações são a base de análises espaciais mais complexas e estão disponíveis no GeoPandas por meio da função gpd.overlay(). Basta indicar as duas camadas, escolher o parâmetro how (como 'intersection', 'union' ou 'difference') e garantir que ambas estejam no mesmo CRS.

Exemplo opcional: sobrepondo pontos das praias a Florianópolis

Use o .plot() em conjunto com o Matplotlib para compor camadas. O script destaca Florianópolis e plota as praias por cima, ótimo para validar se a distribuição espacial está coerente antes de partir para o Folium no capítulo 8.

import geopandas as gpd
import pandas as pd
from shapely.geometry import Point
import matplotlib.pyplot as plt

try:
    gdf_municipios = gpd.read_file('arquivos/SC_Municipios_2022/SC_Municipios_2022.shp')

    df_praias = pd.read_csv('arquivos/Pontos.csv')
    geometry = [Point(xy) for xy in zip(df_praias['Longitude'], df_praias['Latitude'])]
    gdf_praias = gpd.GeoDataFrame(df_praias, geometry=geometry, crs='EPSG:4326')

    gdf_municipios = gdf_municipios.to_crs(gdf_praias.crs)
    florianopolis = gdf_municipios[gdf_municipios['NM_MUN'] == 'Florianópolis']

    fig, ax = plt.subplots(figsize=(10, 10))
    florianopolis.plot(ax=ax, color='#f3f4f6', edgecolor='#0f172a', linewidth=0.8)
    gdf_praias.plot(ax=ax, marker='o', color='#1d4ed8', markersize=60, label='Praias monitoradas')

    ax.set_title('Distribuição das praias monitoradas em Florianópolis', fontsize=16)
    ax.set_axis_off()
    ax.legend()
    plt.show()

except Exception as e:
    print(f"Ocorreu um erro: {e}")

Quer avançar? Troque as praias por buffers (seção 4.8) ou aplique gpd.overlay() para descobrir qual parte dos buffers está dentro de áreas de preservação.

5. Laboratório prático: Consolidando múltiplos shapefiles em GeoPackage

Neste laboratório, vamos criar um fluxo completo que simula uma situação real: você recebeu múltiplos shapefiles de diferentes regiões e precisa consolidá-los em um único arquivo GeoPackage (.gpkg) padronizado, incluindo reprojeção para UTM.

5.1 Preparação do ambiente

Primeiro, vamos organizar nossa estrutura de pastas e preparar alguns dados de exemplo:

# Estrutura de pastas recomendada
projeto_geo/
├── arquivos/
│   ├── shapefiles_originais/
│   │   ├── municipios_SC/
│   │   ├── municipios_PR/
│   │   └── municipios_RS/
│   └── dados_processados/
├── saidas/
└── scripts/

5.2 Script principal do laboratório

Vamos criar um script que automatiza todo o processo de consolidação:

import geopandas as gpd
import pandas as pd
import os
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')

def consolidar_shapefiles_para_geopackage():
    """
    Consolida múltiplos shapefiles em um único GeoPackage
    com reprojeção padronizada para UTM
    """
    
    # === CONFIGURAÇÃO INICIAL ===
    pasta_shapefiles = Path('arquivos/shapefiles_originais')
    arquivo_saida = Path('saidas/dados_consolidados.gpkg')
    crs_destino = 'EPSG:31982'  # SIRGAS 2000 / UTM zone 22S
    
    # Criar pasta de saída se não existir
    arquivo_saida.parent.mkdir(parents=True, exist_ok=True)
    
    print("🚀 Iniciando consolidação de shapefiles...")
    print(f"📁 Pasta de origem: {pasta_shapefiles}")
    print(f"📦 Arquivo de destino: {arquivo_saida}")
    print(f"🗺️  Sistema de coordenadas destino: {crs_destino}")
    print("-" * 50)
    
    # === DESCOBRIR TODOS OS SHAPEFILES ===
    shapefiles_encontrados = []
    
    # Procurar recursivamente por arquivos .shp
    for arquivo_shp in pasta_shapefiles.rglob('*.shp'):
        shapefiles_encontrados.append(arquivo_shp)
    
    if not shapefiles_encontrados:
        print("❌ Nenhum shapefile encontrado na pasta especificada!")
        return
    
    print(f"📋 Encontrados {len(shapefiles_encontrados)} shapefiles:")
    for i, arquivo in enumerate(shapefiles_encontrados, 1):
        print(f"   {i}. {arquivo.name} ({arquivo.parent.name})")
    print("-" * 50)
    
    # === PROCESSAR CADA SHAPEFILE ===
    dados_consolidados = []
    log_processamento = []
    
    for i, arquivo_shp in enumerate(shapefiles_encontrados, 1):
        try:
            print(f"⚙️  Processando {i}/{len(shapefiles_encontrados)}: {arquivo_shp.name}")
            
            # Carregar o shapefile
            gdf = gpd.read_file(arquivo_shp)
            
            # Informações básicas
            crs_original = gdf.crs
            num_features = len(gdf)
            
            print(f"    📊 Features: {num_features}")
            print(f"    🗺️  CRS original: {crs_original}")
            
            # Reprojetar se necessário
            if gdf.crs != crs_destino:
                print(f"    🔄 Reprojetando para {crs_destino}...")
                gdf = gdf.to_crs(crs_destino)
            
            # Adicionar metadados de origem
            gdf['arquivo_origem'] = arquivo_shp.name
            gdf['pasta_origem'] = arquivo_shp.parent.name
            gdf['data_processamento'] = pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')
            
            # Adicionar à lista de dados consolidados
            dados_consolidados.append(gdf)
            
            # Log do processamento
            log_processamento.append({
                'arquivo': arquivo_shp.name,
                'pasta': arquivo_shp.parent.name,
                'crs_original': str(crs_original),
                'num_features': num_features,
                'status': 'sucesso'
            })
            
            print(f"    ✅ Processado com sucesso!")
            
        except Exception as e:
            print(f"    ❌ Erro ao processar: {e}")
            log_processamento.append({
                'arquivo': arquivo_shp.name,
                'pasta': arquivo_shp.parent.name,
                'crs_original': 'erro',
                'num_features': 0,
                'status': f'erro: {str(e)}'
            })
    
    if not dados_consolidados:
        print("❌ Nenhum shapefile foi processado com sucesso!")
        return
    
    print("-" * 50)
    print("🔗 Consolidando dados...")
    
    # === CONSOLIDAR TODOS OS DADOS ===
    try:
        # Concatenar todos os GeoDataFrames
        gdf_final = gpd.GeoDataFrame(pd.concat(dados_consolidados, ignore_index=True))
        
        # Garantir que o CRS está correto
        gdf_final.crs = crs_destino
        
        # Estatísticas finais
        total_features = len(gdf_final)
        arquivos_processados = len([log for log in log_processamento if log['status'] == 'sucesso'])
        
        print(f"📊 Resultado da consolidação:")
        print(f"    ✅ Arquivos processados: {arquivos_processados}/{len(shapefiles_encontrados)}")
        print(f"    📈 Total de features: {total_features}")
        print(f"    🗺️  CRS final: {gdf_final.crs}")
        print(f"    📐 Extent (bounds): {gdf_final.total_bounds}")
        
        # === SALVAR NO GEOPACKAGE ===
        print("-" * 50)
        print("💾 Salvando no GeoPackage...")
        
        # Salvar dados principais
        gdf_final.to_file(arquivo_saida, layer='dados_consolidados', driver='GPKG')
        
        # Criar DataFrame com log de processamento
        df_log = pd.DataFrame(log_processamento)
        
        # Salvar log como tabela adicional (sem geometria)
        # Converter para GeoDataFrame vazio apenas para usar to_file
        gdf_log = gpd.GeoDataFrame(df_log)
        gdf_log.to_file(arquivo_saida, layer='log_processamento', driver='GPKG')
        
        print(f"✅ Dados salvos com sucesso em: {arquivo_saida}")
        print(f"📋 Camadas criadas:")
        print(f"    • dados_consolidados: {total_features} features")
        print(f"    • log_processamento: relatório de processamento")
        
        # === VERIFICAÇÃO FINAL ===
        print("-" * 50)
        print("🔍 Verificação final...")
        
        # Ler de volta para confirmar
        gdf_verificacao = gpd.read_file(arquivo_saida, layer='dados_consolidados')
        print(f"✅ Verificação: {len(gdf_verificacao)} features lidas do GeoPackage")
        
        return gdf_final, df_log
        
    except Exception as e:
        print(f"❌ Erro durante a consolidação: {e}")
        return None, None

# === FUNÇÃO AUXILIAR PARA ANÁLISE ===
def analisar_geopackage(arquivo_gpkg):
    """
    Analisa o conteúdo de um GeoPackage gerado
    """
    try:
        # Listar camadas disponíveis
        import fiona
        with fiona.open(arquivo_gpkg) as f:
            camadas = fiona.listlayers(arquivo_gpkg)
        
        print(f"📦 Análise do GeoPackage: {arquivo_gpkg}")
        print(f"🗂️  Camadas disponíveis: {camadas}")
        
        for camada in camadas:
            try:
                if camada == 'dados_consolidados':
                    gdf = gpd.read_file(arquivo_gpkg, layer=camada)
                    print(f"\n📊 Camada '{camada}':")
                    print(f"    📈 Features: {len(gdf)}")
                    print(f"    🗺️  CRS: {gdf.crs}")
                    print(f"    📋 Colunas: {list(gdf.columns)}")
                    print(f"    📐 Bounds: {gdf.total_bounds}")
                    
                    # Análise por arquivo de origem
                    if 'arquivo_origem' in gdf.columns:
                        contagem = gdf['arquivo_origem'].value_counts()
                        print(f"    📁 Features por arquivo:")
                        for arquivo, count in contagem.items():
                            print(f"        {arquivo}: {count}")
                            
            except Exception as e:
                print(f"    ❌ Erro ao analisar camada {camada}: {e}")
                
    except Exception as e:
        print(f"❌ Erro ao analisar GeoPackage: {e}")

# === EXECUTAR O LABORATÓRIO ===
if __name__ == "__main__":
    # Executar consolidação
    gdf_resultado, df_log = consolidar_shapefiles_para_geopackage()
    
    if gdf_resultado is not None:
        print("\n" + "="*50)
        print("🎉 LABORATÓRIO CONCLUÍDO COM SUCESSO!")
        print("="*50)
        
        # Análise do resultado
        analisar_geopackage('saidas/dados_consolidados.gpkg')
        
        print("\n💡 Próximos passos sugeridos:")
        print("   1. Abra o arquivo .gpkg no QGIS para visualização")
        print("   2. Use o GeoDataFrame resultado para análises espaciais")
        print("   3. Experimente diferentes projeções conforme sua área de estudo")
    
    else:
        print("\n❌ Laboratório não foi concluído. Verifique os erros acima.")

5.3 Como executar o laboratório

Passo 1: Organize seus shapefiles na estrutura de pastas sugerida

Passo 2: Salve o script acima como scripts/consolidar_shapefiles.py

Passo 3: Execute o script a partir da raiz do projeto

cd projeto_geo
python scripts/consolidar_shapefiles.py

5.4 Resultados esperados

Após a execução bem-sucedida, você terá:

  • Um arquivo GeoPackage unificado em saidas/dados_consolidados.gpkg
  • Todas as geometrias reprojetadas para o sistema UTM especificado
  • Metadados de origem preservados em cada feature
  • Log de processamento como camada adicional no GeoPackage
  • Relatório detalhado no console sobre todo o processo

6. Conclusão e próximos passos

Este capítulo forneceu uma base sólida para trabalhar com dados tabulares e geoespaciais em Python. Com Pandas e GeoPandas você agora domina:

  • Instalação e configuração das bibliotecas essenciais usando pip
  • Leitura e manipulação de dados tabulares sem escrever laços repetitivos
  • Trabalho com arquivos espaciais, conversão de formatos e reprojeção de coordenadas
  • Transformação de DataFrames em GeoDataFrames espaciais a partir de coordenadas
  • Junções espaciais para cruzar dados de pontos com polígonos
  • Operações geométricas como buffers para análises de proximidade
  • Fluxos automatizados de consolidação de múltiplos arquivos espaciais
  • Boas práticas de organização de projetos com estrutura de pastas padronizada

O laboratório prático demonstrou como aplicar todos esses conceitos em um cenário real, consolidando múltiplos shapefiles em um formato moderno e eficiente. Essa habilidade é fundamental para projetos de geoprocessamento que lidam com dados de múltiplas fontes.

No próximo capítulo, utilizaremos esses GeoDataFrames e dados processados para criar visualizações interativas com Folium, transformando análises em mapas navegáveis que comunicam resultados de forma efetiva.